#ifndef __tenacitas_periodicity__recurrence__h__
#define __tenacitas_periodicity__recurrence__h__


// =============================================================================
/** @file File description */


// =============================================================================
//                                 C++ Headers
#include <ctime>
#include <cstdint>

// =============================================================================
//                                 3rds Headers


// =============================================================================
//                                 Our Headers


// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants
/** */


// =============================================================================
//                                 Macro Commands
/** */


/** namespace of the organization */
namespace tenacitas {

    /** namespace for the project */
    namespace periodicity {

        // =========================== Typedefs ===========================
        /** */


        // ============================ Pre-Declarations ==================


        // ============================ Attributes ========================
        /** */


        // ============================ Functions =========================
        /** */


        // ============================ Classes ===========================

        /** recurrence

            Defines a recurrence in time, defining a pattern of time
            repetition.
         
            Examples:
            - start: 2009.02.08
            frequency: month
            interval: every one
            criteria: on days 4, 13 and 27
            exception: none
            limit: up to 20 occurrences
         
            - start: 2009.02.08
            frequency: month
            interval: every 3 months
            criteria: on monday of the 2nd week, friday of the 3rd week
            exception: 2009.07.11 2009.08.11
            limit: until 2011.08.25
         
            - start: 2009.02.08
            frequency: week
            interval: every other
            criteria: on thurdays and saturdays
            exception: none
            limit: until 2009.06.05
         
            - start: 2009.02.08
            frequency: month
            interval: every other
            criteria: the following saturday
            exception: none
            limit: until 2009.06.05
         
            - start: 2009.02.08
            frequency: day
            interval: every 2
            criteria:
            exception: none
            limit: until 2009.06.05
         
            - start: 2009.02.08 10:20:00
            frequency: week
            interval: every
            criteria: on monday at 14:00, on wednesday at 16:00, on
            friday at 12:00
            exception: 2009.09.12
            limit: until 2012.05.29 2011.05.29
         
            <p> The type parameters
         
            <b> t_criteria_type </b>
            The type parameter @p t_criteria_type defines the criteria
            to decide which times, from the @p start, should be
            included in the collection of @p time_t objects
            generated.
         
            @note The @p t_criteria_type should provide these methods:
            <tt> time_t first_time ( const time_t & p_time ) const; </tt>
            This method should return the first time of the sequence, based on a @p
            time_t object
         
            <tt>
            bool next( const time_t & p_previous, time_t & p_next ) const;
            </tt>
            This method should return a reference to the next @p
            time_t, from @p p_previous
         
            In the first example above:
            - start: 2009.02.08
            frequency: month
            interval: every one
            criteria: on days 4, 13 and 27
            exception: none
            limit: up to 20 occurrences
            for the month of february, 2009, if this method is called
            with @p p_previous equals to 2009.08.02, @p p_next will be 2009.02.13.
            If called again with 2009.02.13, @p p_next will be 2008.02.27.
         
            The @p t_criteria should also implement the types:
         
            <tt> period_type </tt> which defines the type of period that the
            recurrence will calculate the sequence of @p time_t objects. For
            example, <tt> tenacitas::date::month_type </tt>
         
         
            @note Please, read the documentation for tenacitas::recurrence_criteria
            for a full explanation of how to use and create criterias for
            recurrence calculating time_t objects.
         
            @note Please, read the documentation of the method @p generate below
            for an example of how a criteria is used.
         
            @note Please, also read the tests, as there are a lot of examples of how
            to use criteria objects.
        */
        template <typename t_criteria_type>
        class recurrence {

        public:

            // --- recurrence - Public Internal Types ------------------------

            /** Type for the identifier of the @p recurrence object */
            typedef std::u16t identifier;

            /** Type for the type of criteria */
            typedef t_criteria_type criteria_type;

            /** Type for the type of periodicity */
            typedef typename criteria_type::period_type period_type;

            // --- recurrence - Public Constructors --------------------------

            /** Constructor */
            recurrence ( ) {}

            /** Destructor */
            ~recurrence ( ) {}
            

            // --- recurrence - Public Accessors -----------------------------

            /** Retrieves the identifier of the @p recurrence object */
            const identifier & get_id ( ) const {
                return m_id;
            }

            /** Defines the identifier of the @p recurrence object */
            void set_id ( const identifier & id ) {
                m_id = id;
            }

            // --- recurrence - Public Processors ----------------------------

            /** Generates a collection of @p t_time
              objects based on a pattern that describes which dates
              should  be included, a first date, a number of
              occurrences, and the interval between them.
             
              @param [in] p_first is the first t_time of the
              sequence
             
              @param [in] p_max_periods is the number of repetitions
              of the recurrence
             
              @param [in] p_at_each is the interval of the
              repetititions
             
              @param [in] p_criteria is the criteria that defines which
              @p t_time objects should be included in the
              collection
             
              @param [in] p_but_not_on is a list of @p t_time_objects
              that, even if selected to be part of the sequence,
              should not be included
             
              @return a list of t_time objects
             
              <@code>
              #include <tenacitas.time/date.h>
              #include <tenacitas.time/recurrence.h>
              #include <tenacitas.time/algorithm.h>
              #include <tenacitas.basic/fault.h>
              #include <iostream>
             
              using namespace std;
              using namespace tenacitas;
             
              int main ( ) {
                  // our recurrence criteria
                  monthly_by_day<date> criteria (
              	  // what to do if a calculated date is holiday?
              	  monthly_by_day<date>::create_anyway,
             
              	  // what to do if a calculated date is a weekend?
              	  monthly_by_day<date>::create_anyway,
             
              	  // what to do if a calculated date is a date that should
              	  // not be used?
              	  monthly_by_day<date>::dont_create,
             
              	  // what to do if a calculated date is an invalid date?
              	  monthly_by_day<date>::dont_create,
             
              	  // our list of holidays
              	  list<date> ( ) ) ;
             
                  // these are the days that should be used to create dates
                  criteria.add ( day ( 4 ) ) ;
                  criteria.add ( day ( 7 ) ) ;
                  criteria.add ( day ( 16 ) ) ;
                  criteria.add ( day ( 22 ) ) ;
             
                  // the date where the recurrence starts
                  date first ( day ( 5 ), month::jun, 2009)  ;
             
                  // the number of months of the recurrence
                  u16t max_periods = 2;
             
                  // the interval between the months
                  i8t at_each = 1;
             
                  // creating the recurrence object
                  recurrence<date, monthly_by_day<date> > mr;
             
                  // generting the list of dates
                  list<date> ds = mr.generate ( first, max_periods, at_each,
                                                criteria ) ;
             
                  return 0;
              <@endcode>
             
              What we want: to generate a sequence of
              tenacitas::date objects using a tenacitas::monthly_by_day where the
              days are 4, 7, 16, 22, the first date is 2009.06.05, every month,
              for two months
             
              This code generates the sequence 2009.06.07, 2009.06.16, 2009.06.22,
              2009.07.04, 2009.07.07, 2009.07.16 and 2009.07.22
             */
            list <time_t> generate ( const time_t & p_first,
                                     u16t p_max_periods,
                                     i8t p_at_each,
                                     const criteria_type & p_criteria,
                                     const list <time_t> & p_but_not_on = list <time_t> ( ) ) {

                // the resulting list
                list <time_t> rc;

                // calculating the first date
                time_t t ( p_criteria.first_time ( p_first ) );

                {
                    // seting the new 'first' to a day before, so that
                    // criteria.next_time ( ) considers this time, and
                    // not the second time in the sequence
                    shift <typename time_t::day_type, time_t> sd;
                    t = sd ( t, -1 );
                }

                // an instance of the proper shift functor
                tenacitas::shift <period_type, time_t> shift;

                // for as many periods specified...
                for ( u16t counter = 0; counter < p_max_periods; ++counter ) {

                    // generates a list of dates for that period, and
                    // add it to the final list
                    generate ( t, p_criteria, p_but_not_on, rc );

                    // next period, please!
                    t = shift ( t, p_at_each );
                }

                // now, we delete from the resulting list all times
                // less than the 'p_first' time passed to the method
                typename list <time_t>::iterator up_to = rc.begin ( );
                for ( ; up_to != rc.end ( ); ++up_to ) {
                    if ( *up_to >= p_first ) break;
                }
                rc.erase ( rc.begin ( ), up_to );

                return rc;
            }

            // --- recurrence - Public Operators -----------------------------

            /**  equal to operator */
            bool operator == ( const recurrence & r ) const {
                return ( m_id == r.m_id );
            }

            /** not-equal to operator */
            bool operator != ( const recurrence & r ) const {
                return ( m_id != r.m_id );
            }

            /** greater than operator */
            bool operator > ( const recurrence & r ) const {
                return ( m_id > r.m_id );
            }

            /** less than operator */
            bool operator < ( const recurrence & r ) const {
                return ( m_id < r.m_id );
            }

            /** greater than or equal to operator */
            bool operator >= ( const recurrence & r ) const {
                return ( m_id >= r.m_id );
            }

            /** less than or equal to operator */
            bool operator <= ( const recurrence & r ) const {
                return ( m_id <= r.m_id );
            }


        protected:

            // --- recurrence - Protected Processors -------------------------

            /** Calculates a collection of a type of t_time between
                a first t_time object, that comform to a pattern of (type
                of) time, and that are not in another collection that define exceptions
             
                @param [in] p_first is the first t_time of the sequence
             
                @param [in] p_criteria is the criteria that defines which @p
                t_time objects should be included in the collection
             
                @param [in] p_but_not_on is a list of @p t_time objects that,
                even if selected to be part of the sequence, should not be
                included
             
                @param [in-out] p_result the t_time objects calculated shall
                be appended to this list
            */
            void generate ( const time_t & p_first,
                            const criteria_type & p_criteria,
                            const list <time_t> & p_but_not_on,
                            list <time_t> & p_result ) {

                // we copy the first t_time object, creating a reference
                time_t new_time ( p_first );

                while ( true ) {

                    // trying to find the next t_time object that from a
                    // current t_time, that satisfies a criteria
                    if ( !p_criteria.next_time ( new_time, p_but_not_on, new_time ) ) {

                        // no time after the current time was found
                        break;
                    }

                    //  we append it to the final list
                    p_result.push_back ( new_time );
                }
            }

        private:
            // --- recurrence - Private Attributes ---------------------------

            /** Identifier of the @p recurrence object */
            const identifier m_id;

        };
    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::periodicity;


#endif
